题目描述:
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
进阶:
- 你可以设计时间复杂度为 $O(n^2)$ 的解决方案吗?
- 你能将算法的时间复杂度降低到 $O(n\log(n))$ 吗?
示例 1:
1 | 输入:nums = [10,9,2,5,3,7,101,18] |
示例 2:
1 | 输入:nums = [0,1,0,3,2,3] |
示例 3:
1 | 输入:nums = [7,7,7,7,7,7,7] |
提示:
- $1 <= nums.length <= 2500$
- $-10^4 <= nums[i] <= 10^4$
链接:
https://leetcode-cn.com/problems/longest-increasing-subsequence
题目分析
1.动态规划
我们思考子序列之间的联系,如果 nums[j]
是递增子序列中的最后一个数,则我们只需要满足 nums[i] > nums[j]
且其中 i > j
,则 nums[i]
可以接在以 nums[j]
后面成为更长的递增子序列,这样的关联是满足动态规划性质的。而对于每个 i
,满足这样关系的 j
可以是多个的,我们需要找到最长的那一个。
我们定义 dp[i]
为以 nums[i]
为结尾的最长递增子序列。那么我们有状态转移方程:$dp[i]=max(dp[j])+1$,其中 $0\leq j < i$ 且 $nums[j]< nums[i]$。最短的递增子序列就只包含本身,长度为 1,因此我们将所有的初始状态赋值为 1。对于每个状态,我们都需要遍历前面所有的状态找到最大值,需要进行双层遍历,满足第一个进阶要求。而我们所要求的结果就是这个过程中出现的最大状态值。
1 | class Solution { |
时间复杂度:$O(n^2)$,其中 $n$ 是数组的大小。每一个状态依赖于前面的所有状态,需要进行双层遍历。
空间复杂度:$O(n)$,其中 $n$ 是数组的大小。我们需要一个数组保存所有的状态,状态数为 $n$。
2.贪心+二分 - 官方题解
这个解法我没有想出来,看了官方题解之后看懂了,在这里用我的话重述。对于同样长度的递增子序列,哪一个是更优的?应该是最后一个数更小的那一个,因为它提供了下一个数更多的可能,换句话说,让序列上升的“慢一些”。那我们使用 greed[i]
表示长度为 i
的递增子序列中最小的末尾元素。这个数组是单调递增的。为什么呢?
我们采用反证法,假设存在 i > j
使得 greed[i] < greed[j]
,那么在长度为 i
的子序列 A 中,前 j
个数也可以构成一个递增子序列 B,并且这个长度为 j
的子序列 B 的末尾元素一定小于 greed[i]
(因为它就包含在 A 中),那么就与 greed[i] < greed[j]
矛盾了。
这个数组是单调的有什么好处?单调的数组进行搜索的时候就可以使用二分法了。如何对这个数组进行更新呢?我们顺序遍历原数组,假设我们遍历到的数比 greed
的最后一个元素大,这意味着这个数可以构成更长的递增子序列,则我们将其添加到 greed
之后,表示新的最长长度子序列以这个数结尾。
如果遍历到的数 nums[i]
不比 greed
最后一个元素大,思考一下对 greed
数组中的数的影响。假设 greed[0 ~ k]
是小于 nums[i]
的所有数,则对于这些数来说没有影响,因为我们本来就要让 greed
值尽可能小。而对于 greed[k+1]
,这个时候我们可以用 greed[k]
代表的 k
个数加上 nums[i]
组成一个长度为 k+1
的递增子序列,也即 greed[k+1]
的值可以更新为 nums[i]
。对于后面的所有数,由于 greed[k+1 ~ n] >= nums[i]
,则 nums[i]
对它们也没有影响。
通过上面的分析,我们可以知道 nums[i]
只需要对单调数组 greed
中的第一个不小于它的值进行替换,则可以使用二分法搜索。这样也达到了我们第二个进阶要求中的对数复杂度。
最后的答案就是 greed
数组的最大下标,而且 greed[0]
是没有作用的,我们可以直接全体前移一位,greed[i]
表示长度为 i+1
的递增子序列中最小的末尾元素。那么答案就刚好是 greed
数组的大小。
1 | class Solution { |
时间复杂度:$O(n\log n)$,其中 $n$ 是数组的大小。我们遍历了数组对 greed
进行更新,而每次更新操作的时间复杂度为 $O(1)$(插入最后)或者 $O(\log n)$(二分法更新中间值)。
空间复杂度:$O(n)$,其中 $n$ 是数组的大小。我们需要一个 greed
数组,最大长度为 $n$。
PS:感觉还是讲不清楚第二种方法,重点在于理解 greed
数组中每个数所代表的含义(千万注意 greed
并不代表最终的递增子序列),应该就可以明白为什么只需要更新不小于 nums[i]
的第一个值了。